/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.java.disco;

import eu.hansolo.jdktools.Architecture;
import eu.hansolo.jdktools.ArchiveType;
import eu.hansolo.jdktools.Latest;
import eu.hansolo.jdktools.LibCType;
import eu.hansolo.jdktools.OperatingSystem;
import eu.hansolo.jdktools.PackageType;
import eu.hansolo.jdktools.TermOfSupport;
import eu.hansolo.jdktools.versioning.VersionNumber;
import io.foojay.api.discoclient.pkg.Distribution;
import io.foojay.api.discoclient.pkg.Pkg;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JLabel;
import org.checkerframework.checker.guieffect.qual.UIEffect;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

@NbBundle.Messages({
    "QuickPanel.searching=Searching...",
    "QuickPanel.error=No matching JDK found."
})
public class QuickPanel extends javax.swing.JPanel {

    private final DefaultComboBoxModel<Distribution> distrosModel = new DefaultComboBoxModel<>();
    private final SelectPackagePanel parentPanel;

    private Pkg pkg;
    private boolean ignoreChanges;

    @Deprecated
    public QuickPanel() {
        this(null);
    }

    QuickPanel(SelectPackagePanel parentPanel) {
        this.parentPanel = parentPanel;
        initComponents();
        distributionComboBox.setRenderer(new DistributionListCellRenderer());
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        javax.swing.JLabel versionLabel = new javax.swing.JLabel();
        versions = new javax.swing.JSlider();
        javax.swing.JLabel distributionLabel = new javax.swing.JLabel();
        distributionComboBox = new javax.swing.JComboBox<>();
        pkgLabel = new javax.swing.JLabel();
        pkgInfo = new javax.swing.JLabel();

        org.openide.awt.Mnemonics.setLocalizedText(versionLabel, org.openide.util.NbBundle.getMessage(QuickPanel.class, "QuickPanel.versionLabel.text")); // NOI18N

        versions.setMinorTickSpacing(1);
        versions.setPaintLabels(true);
        versions.setPaintTicks(true);
        versions.setSnapToTicks(true);
        versions.setInverted(true);
        versions.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                versionsStateChanged(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(distributionLabel, org.openide.util.NbBundle.getMessage(QuickPanel.class, "QuickPanel.distributionLabel.text")); // NOI18N

        distributionComboBox.setModel(createDistrosModel());
        distributionComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                distributionComboBoxActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(pkgLabel, org.openide.util.NbBundle.getMessage(QuickPanel.class, "QuickPanel.pkgLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(pkgInfo, "<PKG_INFO>"); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(versionLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(versions, javax.swing.GroupLayout.DEFAULT_SIZE, 399, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(distributionLabel)
                            .addComponent(pkgLabel))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(pkgInfo)
                            .addComponent(distributionComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addGap(23, 23, 23)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(versionLabel)
                    .addComponent(versions, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(distributionLabel)
                    .addComponent(distributionComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(pkgLabel)
                    .addComponent(pkgInfo))
                .addContainerGap(102, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void distributionComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_distributionComboBoxActionPerformed
        updatePackage();
    }//GEN-LAST:event_distributionComboBoxActionPerformed

    private void versionsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_versionsStateChanged
        if (!versions.getValueIsAdjusting()) {
            updatePackage();
        }
    }//GEN-LAST:event_versionsStateChanged

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JComboBox<Distribution> distributionComboBox;
    private javax.swing.JLabel pkgInfo;
    private javax.swing.JLabel pkgLabel;
    private javax.swing.JSlider versions;
    // End of variables declaration//GEN-END:variables

    private ComboBoxModel<Distribution> createDistrosModel() {
        return distrosModel;
    }

    public void updateDistributions(List<Distribution> distributions, Distribution defaultDist) {
        ignoreChanges = true;
        distrosModel.removeAllElements();
        distributions.stream()
                .sorted(Comparator.comparing(Distribution::getUiString))
                .forEachOrdered(distrosModel::addElement);
        distributionComboBox.setSelectedItem(defaultDist);
        ignoreChanges = false;
    }

    //The `versions` slider holds *the index* in versionsJDKs and
    //this model holds the actual JDKs and the mapping between
    static class VersionsModel {

        private final List<Integer> versionJDKs;
        private final Map<Integer, TermOfSupport> lts;
        private final int currentJdk;

        private VersionsModel(List<Integer> jdks, Map<Integer, TermOfSupport> lts, int currentJdk) {
            this.versionJDKs = new ArrayList<>(jdks);
            //quick panel shows only maintained JDKs
            versionJDKs.removeIf(v -> !lts.containsKey(v));
            versionJDKs.removeIf(v -> v < 8 || v > currentJdk);
            this.lts = lts;
            this.currentJdk = currentJdk;
        }

        public int getMinimum() {
            return 0;
        }

        public int getMaximum() {
            return versionJDKs.size() - 1;
        }

        public int getDefaultValue() {
            return versionJDKs.indexOf(LTSes.latest(lts));
        }

        @UIEffect
        @NonNull
        public Hashtable<Integer, JLabel> createLabels() {
            Hashtable<Integer, JLabel> labels = new Hashtable<>();
            for (Integer v : versionJDKs) {
                boolean isLTS = lts.get(v) == TermOfSupport.LTS;
                JLabel label = new JLabel();
                StringJoiner info = new StringJoiner(", ", " (", ")");
                info.setEmptyValue("");
                if (v == currentJdk) {
                    info.add("latest");
                }
                if (isLTS) {
                    info.add("LTS");
                    //these decorations do nothing on macOS...
                    Font font = label.getFont();
                    label.setFont(font.deriveFont(font.getStyle() | Font.BOLD));
                    label.setToolTipText("Long Term Support");
                }
                label.setText(v.toString() + info.toString());
                labels.put(versionJDKs.indexOf(v), label);
            }
            return labels;
        }

        private int getJDK(int index) {
            return versionJDKs.get(index);
        }

        private int indexOf(int version) {
            return versionJDKs.indexOf(version);
        }

    }

    @MonotonicNonNull
    private VersionsModel versionsModel;

    void setVersions(List<Integer> jdks, Map<Integer, TermOfSupport> lts, int current) {
        ignoreChanges = true;
        versionsModel = new VersionsModel(jdks, lts, current);
        versions.setMaximum(versionsModel.getMaximum());
        versions.setMinimum(versionsModel.getMinimum());
        versions.setLabelTable(versionsModel.createLabels());
        versions.setValue(versionsModel.getDefaultValue());
        ignoreChanges = false;
    }

    void initFocus() {
        versions.requestFocusInWindow();
        updatePackage();
    }

    void switchFocus(Distribution distribution, Integer version) {
        ignoreChanges = true;
        if (distribution != null) {
            distributionComboBox.setSelectedItem(distribution);
        }
        if (version != null) {
            int versionIdx = versionsModel.indexOf(version);
            if (versionIdx >= 0) {
                versions.setValue(versionIdx);
            }
        }
        ignoreChanges = false;
        initFocus();
    }

    Distribution getSelectedDistribution() {
        return (Distribution) distributionComboBox.getSelectedItem();
    }

    Integer getSelectedVersion() {
        return versionsModel.getJDK(versions.getValue());
    }

    Pkg getSelectedPackage() {
        return pkg;
    }

    private void updatePackage() {

        if (ignoreChanges) {
            return;
        }

        pkg = null;
        parentPanel.fireValidityChange();
        pkgInfo.setText(Bundle.QuickPanel_searching());

        Distribution distribution = (Distribution) distributionComboBox.getSelectedItem();
        VersionNumber version = new VersionNumber(versionsModel.getJDK(versions.getValue()));
        OperatingSystem operatingSystem = OS.getOperatingSystem();
        Architecture architecture = OS.getArchitecture();
        SwingWorker2.submit(() -> {
            List<Pkg> pkgs = Client.getInstance().getPkgs(distribution,
                    version,
                    Latest.AVAILABLE,
                    operatingSystem,
                    architecture,
                    ArchiveType.NONE,
                    PackageType.JDK,
                    false,
                    false);
            return pkgs.stream()
                    .filter(p -> validatePackage(operatingSystem, p))
                    .findFirst();
        }).then(p -> {
            if (p.isPresent()) {
                pkg = p.get();
                pkgInfo.setText(pkg.getFileName());
            } else {
                pkg = null;
                pkgInfo.setText(Bundle.QuickPanel_error());
            }
            parentPanel.fireValidityChange();
        }).handle(ex -> {
            Exceptions.printStackTrace(ex);
            pkg = null;
            pkgInfo.setText(Bundle.QuickPanel_error());
        }).execute();
    }

    private boolean validatePackage(OperatingSystem os, Pkg p) {
        switch (os) {
            case MACOS:
                return ArchiveType.ZIP == p.getArchiveType() || ArchiveType.TAR_GZ == p.getArchiveType();
            case LINUX:
                return (ArchiveType.ZIP == p.getArchiveType() || ArchiveType.TAR_GZ == p.getArchiveType())
                        && (LibCType.MUSL != p.getLibCType());
            case WINDOWS:
            default:
                return ArchiveType.ZIP == p.getArchiveType();
        }
    }

}
